﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the Microsoft Public License (Ms-PL).
// See accompanying file License.txt or copy at http://mews.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Mews.Net
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>Is the base class of all commands.</summary>
    /// <typeparam name="TClient">The type of the client the command is for.</typeparam>
    /// <typeparam name="TResponse">The type of the response the command will produce.</typeparam>
    /// <remarks>Concrete commands never derive directly from this class, but subclass protocol-specific variants, like
    /// e.g. <see cref="Nntp.SingleLineCommand{T}"/> or <see cref="Nntp.NonPipelinableCommand{T}"/>.</remarks>
    /// <threadsafety static="true" instance="false"/>
    public abstract class CommandBase<TClient, TResponse> : ICommand<TClient>
        where TClient : IClient<TClient>
        where TResponse : IResponse
    {
        /// <summary>Executes this command on <paramref name="client"/>.</summary>
        /// <param name="client">The client to execute the command on.</param>
        /// <returns>The response returned from the server.</returns>
        /// <exception cref="ArgumentException">
        /// <paramref name="client"/>.<see cref="IClient{T}.PendingCommandsCount"/> > 0.</exception>
        /// <remarks>
        /// <para>The command is executed by a call to <see cref="IClient{T}.EnqueueCommand"/> immediately followed by a
        /// call to <see cref="IClient{T}.GetResponse"/>. This is only possible when the number of pending commands is
        /// zero.</para>
        /// <para>See <see cref="IClient{T}.GetResponse"/> for information about additional exceptions that might be
        /// thrown.</para>
        /// </remarks>
        public TResponse Execute(TClient client)
        {
            if (client.PendingCommandsCount != 0)
            {
                throw new ArgumentException(
                    "A command cannot be executed immediately when the command/response queue is not empty", "client");
            }

            client.EnqueueCommand(this);
            return (TResponse)client.GetResponse();
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="ICommand{T}.IsPipelinable"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "Clients never need to call this property.")]
        bool ICommand<TClient>.IsPipelinable
        {
            get { return this.IsPipelinable; }
        }

        /// <summary>See <see cref="ICommand{T}.SendCommand"/>.</summary>
        void ICommand<TClient>.SendCommand(CreateCommandStreamCallback createCommandStreamCallback)
        {
            this.SendCommand(createCommandStreamCallback);
        }

        /// <summary>See <see cref="ICommand{T}.ReceiveResponse"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "ReceiveResponseCore calls the protected ReceiveResponse in subclasses.")]
        IResponse ICommand<TClient>.ReceiveResponse(
            CreateResponseStreamCallback createResponseStreamCallback,
            CreateCommandStreamCallback createCommandStreamCallback)
        {
            return this.ReceiveResponseCore(createResponseStreamCallback, createCommandStreamCallback);
        }

        /// <summary>See <see cref="ICommand{T}.IsMultilineResponse"/>.</summary>
        bool ICommand<TClient>.IsMultilineResponse(IList<string> initialResponseLineWords)
        {
            return this.IsMultilineResponse(initialResponseLineWords);
        }

        /// <summary>See <see cref="ICommand{T}.ThrowOnError"/>.</summary>
        void ICommand<TClient>.ThrowOnError(IList<string> initialResponseLineWords)
        {
            this.ThrowOnError(initialResponseLineWords);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        internal CommandBase()
        {
        }

        internal abstract bool IsPipelinable
        {
            get;
        }

        internal abstract TResponse ReceiveResponseCore(
            CreateResponseStreamCallback createResponseStreamCallback,
            CreateCommandStreamCallback createCommandStreamCallback);

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="ICommand{T}.SendCommand"/>.</summary>
        protected abstract void SendCommand(CreateCommandStreamCallback createCommandStreamCallback);

        /// <summary>See <see cref="ICommand{T}.IsMultilineResponse"/>.</summary>
        protected abstract bool IsMultilineResponse(IList<string> initialResponseLineWords);

        /// <summary>See <see cref="ICommand{T}.ThrowOnError"/>.</summary>
        protected abstract void ThrowOnError(IList<string> initialResponseLineWords);
    }
}
